import bpy
# import random
from uuid import uuid4
from bpy.props import BoolProperty
from ...addon.naming import FluidLabNaming
from bpy.types import Operator, Context, Scene, Object
from ...libs.functions.object import name_with_zfill, add_single_vertext_ob
from ...libs.functions.modifiers import create_modifier
from ...libs.functions.get_common_vars import get_common_vars
from ...libs.functions.particles import create_particle_system
from ...libs.functions.geometry_nodes import append_gn, set_exposed_attributes_of_gn
# from ...libs.functions.basics import enter_edit_mode, enter_object_mode
from ...libs.functions.collections import set_active_collection_by_name, create_new_collection, remove_collection_if_is_empty



class FLUIDLAB_OT_fluid_emitters_list_add(Operator):
    bl_idname = "fluidlab.fluid_emitters_list_add"
    bl_label = "Create New Emitter"
    bl_description = "Create Emitter to Fluid Emitter List"
    bl_options = {"REGISTER", "UNDO"}

    emitters_list_is_void: BoolProperty(default=True)


    def setup_particle_system(self, context:Context, scn:Scene, ob:Object, emitter_type:str, emission_props, physics_props) -> None:
        
        d_props = {

            # Las propiedades comunes:
            'COMMON': {
                'ps_name': FluidLabNaming.PS_NAME,
                'lifetime': scn.frame_end if self.emitters_list_is_void else emission_props.lifetime,
                'lifetime_random': emission_props.get_default_properties("lifetime_random") if self.emitters_list_is_void else emission_props.lifetime_random,
                'particle_size': emission_props.get_default_properties("size") if self.emitters_list_is_void else emission_props.prev_size,
                'display_color': 'VELOCITY' if self.emitters_list_is_void else emission_props.display_color,
                'color_maximum': 10,
                'display_size': 0.05 if self.emitters_list_is_void else emission_props.size,
                'physics_type': 'FLUID',
                'mass': physics_props.get_default_properties("mass") if self.emitters_list_is_void else physics_props.mass,
                'use_multiply_size_mass': physics_props.get_default_properties("use_multiply_size_mass") if self.emitters_list_is_void else physics_props.use_multiply_size_mass,
                'stiffness': physics_props.get_default_properties("stiffness") if self.emitters_list_is_void else physics_props.stiffness,
                'drag_factor': physics_props.get_default_properties("drag_factor") if self.emitters_list_is_void else physics_props.drag_factor,
                'damping': physics_props.get_default_properties("damping") if self.emitters_list_is_void else physics_props.damping,
                'use_size_deflect': physics_props.get_default_properties("use_size_deflect") ,
                'integrator': 'VERLET',
                'subframes': physics_props.get_default_properties("subframes"),
                'stiff_viscosity': physics_props.get_default_properties("stiff_viscosity") if self.emitters_list_is_void else physics_props.stiff_viscosity,
                'spring_force': 0,
                'linear_viscosity': physics_props.get_default_properties("linear_viscosity") if self.emitters_list_is_void else physics_props.linear_viscosity,
                'normal_factor': 0,
            },

            # Las propiedades si son Geometry:
            'GEOMETRY': {
                'distribution': 'GRID',
                'emit_from': 'VOLUME',
                'count': emission_props.get_default_properties("count") if self.emitters_list_is_void else emission_props.count,
                'frame_end': 1 if self.emitters_list_is_void else emission_props.frame_end,
                'grid_resolution': emission_props.get_default_properties("grid_resolution") if self.emitters_list_is_void else emission_props.grid_resolution,  
            },

            # Las propiedades si son Inflow:
            'INFLOW': {
                "distribution": 'JIT',
                "emit_from": 'FACE',
                "count": emission_props.get_default_properties("count") if self.emitters_list_is_void else emission_props.count,
                "inflow_resolution": emission_props.get_default_properties("inflow_resolution") if self.emitters_list_is_void else emission_props.inflow_resolution,
                "frame_end": 100 if self.emitters_list_is_void else emission_props.frame_end,
                "grid_resolution": 20,
            }
        }

        # Para que al crearse ya esten seteadas:
        psys = create_particle_system(
                                        context                 = context, 
                                        ob                      = ob, 
                                        ps_name                 = d_props['COMMON']["ps_name"],
                                        emit_from               = d_props[emitter_type]["emit_from"],
                                        ps_count                = d_props[emitter_type]["count"],
                                        frame_end               = d_props[emitter_type]["frame_end"],
                                        lifetime                = d_props['COMMON']["lifetime"],
                                        lifetime_random         = d_props['COMMON']["lifetime_random"],
                                        distribution            = d_props[emitter_type]["distribution"],
                                        grid_resolution         = d_props[emitter_type]["grid_resolution"],
                                        particle_size           = d_props['COMMON']["particle_size"], # size
                                        display_color           = d_props['COMMON']["display_color"],
                                        color_maximum           = d_props['COMMON']["color_maximum"],
                                        display_size            = d_props['COMMON']["display_size"], # size
                                        physics_type            = d_props['COMMON']["physics_type"],
                                        mass                    = d_props['COMMON']["mass"],
                                        use_multiply_size_mass  = d_props['COMMON']["use_multiply_size_mass"],
                                        stiffness               = d_props['COMMON']["stiffness"],
                                        drag_factor             = d_props['COMMON']["drag_factor"],
                                        damping                 = d_props['COMMON']["damping"],
                                        use_size_deflect        = d_props['COMMON']["use_size_deflect"],
                                        integrator              = d_props['COMMON']["integrator"],
                                        subframes               = d_props['COMMON']["subframes"],
                                        stiff_viscosity         = d_props['COMMON']["stiff_viscosity"],
                                        spring_force            = d_props['COMMON']["spring_force"],
                                        linear_viscosity        = d_props['COMMON']["linear_viscosity"],
                                        normal_factor           = d_props['COMMON']["normal_factor"]
        )

        # Para que la ui conhincida con los settings, seteo mis active_gourp properties:
        emission_props.emit_from = d_props[emitter_type]["emit_from"]
        emission_props.frame_end = d_props[emitter_type]["frame_end"]     
        
        if emitter_type == 'GEOMETRY':
            emission_props.grid_resolution = d_props[emitter_type]["grid_resolution"]
        
        elif emitter_type == 'INFLOW':
            emission_props.size = d_props['COMMON']["particle_size"]
            emission_props.prev_size = emission_props.size
            emission_props.inflow_resolution = d_props[emitter_type]["inflow_resolution"]

        psys.point_cache.name = ob.name

        # Marcamos nuestras particulas:
        psys.settings.fluidlab.id_name = str(uuid4())[:6]


    def execute(self, context):
        
        scn, fluid_groups, ui = get_common_vars(context, get_scn=True, get_fluid_groups=True, get_ui=True)

        valid_objects = [ob for ob in context.view_layer.objects.selected if ob.type == 'MESH']
        if not valid_objects:
            self.report({'ERROR'}, "Invalid object selection!")
            return {'CANCELLED'}
        
        if fluid_groups.is_void:
            return {'CANCELLED'}
        
        active_group = fluid_groups.active

        if not active_group:
            return {'CANCELLED'}
        
        group_coll = active_group.group_coll
        if not group_coll:
            return {'CANCELLED'}
        
        self.emitters_list_is_void = active_group.emitters.is_void
        
        # Impedimos agregar fluidos si hay algún collider en la selección:
        collider_name = next((ob.name for ob in valid_objects for mod in ob.modifiers if mod.type == 'COLLISION'), None)
        if collider_name:
            self.report({'ERROR'}, f"Object {collider_name} is a collider!")
            return {'CANCELLED'}
                
        emitter_type = active_group.emitter_type

        set_active_collection_by_name(context, group_coll.name)

        for ob in valid_objects:

            have_ps = next((ps for ps in ob.particle_systems if ps.settings.fluidlab.id_name != ""), None)
            if have_ps:
                continue

            # Deslinko de donde esten, excepto de RigidBodyWorld
            [c.objects.unlink(ob) for c in ob.users_collection if c.name != "RigidBodyWorld"]       

            # Lo muevo a su coleccion FluidLab:
            group_coll.objects.link(ob)

            # Si el objeto emitter no tiene materiales asignados, agrega un nuevo material (uno diferente por cada objeto):
            if len(ob.data.materials) == 0:
                new_mat = bpy.data.materials.new(name=FluidLabNaming.MAT_BLEND_BASIC + "_" + ob.name)
                new_mat[FluidLabNaming.FLUIDLAB_MAT] = True
                ob.data.materials.append(new_mat)

            ob.show_instancer_for_render = False
            ob.show_instancer_for_viewport = False
            # ob.display_type = 'WIRE'

            # Lo marco como emitter_ob:
            ob[FluidLabNaming.EMITTER_OB] = True

            # Guardo la data para luego agregarlos al listado 
            # Porque si lo hago ahora el FluidEmittersList.list_index_update me lo pisaría el seteo al cargar en ui la info:
            id_name = str(uuid4())[:6]
            emitters_list = active_group.emitters
            # emitters_list.add_item(id_name, ob.name, ob)

            # preparando las colecciones de Vertex:
            coll_name = active_group.group_coll.name

            # FluidLab_Vertex:
            fl_vertext_coll = bpy.data.collections.get(FluidLabNaming.VERTEX_COLL)
            if not fl_vertext_coll:
                fl_vertext_coll = create_new_collection(context, FluidLabNaming.VERTEX_COLL)

            fl_vertext_coll[FluidLabNaming.FluidLab] = True
            set_active_collection_by_name(context, fl_vertext_coll.name)

            ## Intermediate collection Vertex_coll_name:
            place = bpy.data.collections
            desired_name = FluidLabNaming.VERTEX_PREFIX + coll_name.replace(FluidLabNaming.GROUP_PREFIX, "") if "_" in coll_name else coll_name

            inter_coll = bpy.data.collections.get(desired_name)
            if not inter_coll:
                intermediate_name = name_with_zfill(context, desired_name, place)
            
                inter_coll = bpy.data.collections.get(intermediate_name)
                if not inter_coll:
                    inter_coll = create_new_collection(context, intermediate_name, False)

            # Marcamos la collection como de tipo Vertex (para poder filtralas mejor):
            inter_coll[FluidLabNaming.FluidLab] = True
            inter_coll[FluidLabNaming._VERTEX_COLL] = True

            # Guardo el vertex collection en el active group:
            active_group.vertex_coll = inter_coll

            set_active_collection_by_name(context, inter_coll.name)
        
            # Creo los single vertex para el particle instance (Primero los Alive, luego los Dead):
            
            # Esto lo uso para el workflow con alembic, para relacionar los objetos entre si:
            name_id = str(uuid4())[:6]

            single_vert_name = FluidLabNaming.VERTEX_PREFIX + name_id + "_Alive"
            single_vert_ob = add_single_vertext_ob(ob_name=single_vert_name, mesh_name=single_vert_name, ob_coords=(0, 0, 0), vtx_coords=(0, 0, 0), collection=inter_coll)
            if single_vert_ob:

                # ocultamos con el ojito y en el render:
                single_vert_ob.hide_set(True)
                single_vert_ob.hide_render = True

                # Duplicamos para el Dead en low level:
                dead_ob = single_vert_ob.copy()
                dead_ob.data = single_vert_ob.data.copy()

                dead_ob.name = FluidLabNaming.VERTEX_PREFIX + name_id + "_Dead"
                # lo linkeo a las collections del original:
                for coll in single_vert_ob.users_collection:
                    coll.objects.link(dead_ob)
                
                # Lo marcamos como Alive:
                single_vert_ob[FluidLabNaming.ALIVE] = True
                
                # Lo marcamos como Dead:
                dead_ob[FluidLabNaming.DEAD] = True
                
                # ocultamos con el ojito y en el render:
                dead_ob.hide_set(True)
                dead_ob.hide_render = True

                # Creamos el modifier de Particle Instances para el Alive:
                part_inst_alive_mod = create_modifier(single_vert_ob, FluidLabNaming.PARTICLE_INSTANCE_ALIVE_MOD, 'PARTICLE_INSTANCE')
                part_inst_alive_mod.object = ob
                part_inst_alive_mod.show_alive = True
                part_inst_alive_mod.show_dead = True
                part_inst_alive_mod.show_unborn = False
                single_vert_ob[FluidLabNaming.PARTICLE_INSTANCER_OB] = True

                # Creamos el modifier de Particle Instances para el Dead:
                part_inst_dead_mod = create_modifier(dead_ob, FluidLabNaming.PARTICLE_INSTANCE_DEAD_MOD, 'PARTICLE_INSTANCE')
                part_inst_dead_mod.object = ob
                part_inst_dead_mod.show_alive = False
                part_inst_dead_mod.show_dead = True
                part_inst_dead_mod.show_unborn = False
                dead_ob[FluidLabNaming.PARTICLE_INSTANCER_OB] = True

                # Agregamos el GN de Alive al Vertex object (hacer un appedn si no existe el nodo, pero todos los vertex_ob usarán el mismo node_group):
                node_group = append_gn(context=context, file_name="Alive_GN", gn_name=FluidLabNaming.GN_PART_ATTRS, only_if_not_exist=True, is_unique=False)
                gn_mod = create_modifier(single_vert_ob, FluidLabNaming.GN_MESH_MOD, 'NODES')
                if gn_mod:
                    gn_mod.node_group = node_group
                    # Les ponemos un dead ob en cada Object:
                    set_exposed_attributes_of_gn(gn_mod, "Object", dead_ob, debug=False)
                    # Les ponemos un color random a cada uno (esto ya no se hace aquí):
                    # set_exposed_attributes_of_gn(gn_mod, "Color", (random.random(), random.random(), random.random(), 1), debug=False)


                # lo agrego al listado:
                emitters_list.add_item(id_name, ob.name, group_coll, ob, single_vert_ob, dead_ob)
                
                # Configuro las particles del objeto emitter:
                emitter_item = fluid_groups.get_first_global_emitter(section_props="global_mode_emission_sect", without_self_ob=active_group.emitters.is_void, target="item")
                emission_props = emitter_item.emission
                physics_props  = emitter_item.physics
                self.setup_particle_system(context, scn, ob, emitter_type, emission_props, physics_props)

        #--------------------------------------------------------------------------------------------------------------------
        # Cosas varias Finales:
        #--------------------------------------------------------------------------------------------------------------------

        # Si se quedó la collection por default de blender Collection vacia, la borramos:
        remove_collection_if_is_empty(context, "Collection")
        
        set_active_collection_by_name(context, scn.collection.name)        

        # llevamos al usuario a Fluid Settigs:
        if ui.main_modules != 'FLUID_SETTINGS':
            ui.main_modules = 'FLUID_SETTINGS'
   
        return {'FINISHED'}